import http, urllib, json, logging
from dataclasses import dataclass

@dataclass
class NacosServiceRegistry:
    '''Nacos registry unit
    '''
    namespace: str # 服务注册命名空间
    group_name: str # group in nacos
    service_name: str # 服务注册名称 
    ip: str # service self ip
    port: int # service self listening port
    weight: int # instance self weight

logger = logging.getLogger(__name__)

class NacosClient:
    '''Simple nacos API client
    '''

    def __init__(self, host, port):
        self._host = host
        self._port = port
        self._headers = {
            "Content-Type": "application/json"
        }

    def _is_ok(self, resp):
        return resp.status > 199 and resp.status < 300

    def get_service_names(self, namespace_id, group_name: str):
        conn = http.client.HTTPConnection(self._host, self._port)
        query = {}
        if namespace_id:
            query["namespaceId"] = namespace_id
        if group_name:
            query["groupName"] = group_name

        query["pageNo"] = 0
        query["pageSize"] = 1000
        query = urllib.parse.urlencode(query)

        path = f"/nacos/v1/ns/service/list?{query}"
        try:
            conn.request("GET", path, headers = self._headers)
            resp = conn.getresponse()
            if not self._is_ok(resp):
                return None
            resp = resp.read()
            resp = json.loads(resp)
            return resp["doms"]
        finally:
            conn.close()

    def register_inst(self, inst: NacosServiceRegistry):
        '''将服务实例注册到nacos
        '''
        query = {}
        # body["id"] = "%s-local-%d" % (service_name, port)
        query["ip"] = inst.ip
        query["port"] = inst.port
        if inst.namespace is not None:
            query["namespaceId"] = inst.namespace
        if inst.group_name is not None:
            query["groupName"] = inst.group_name
        query["serviceName"] = inst.service_name
        query["weight"] = 100 if inst.weight is None else inst.weight
        query["healthy"] = True

        query = urllib.parse.urlencode(query)
        path = "%s?%s" % ("/nacos/v1/ns/instance", query)

        conn = http.client.HTTPConnection(self._host, self._port)
        try:
            conn.request("POST", path, headers = self._headers)
            resp = conn.getresponse()
            if self._is_ok(resp):
                return query
            resp = resp.read()
            print(str(resp, 'utf8'))
        finally:
            conn.close()

    def instance_beat(self, inst: NacosServiceRegistry):
        '''向nacos服务器发送某个服务的心跳
        '''
        query = {}
        query["serviceName"] = inst.service_name
        if inst.namespace is not None:
            query["namespaceId"] = inst.namespace
        if inst.group_name is not None:
            query["groupName"] = inst.group_name
        query["ephemeral"] = False
        query["ip"] = inst.ip
        query["port"] = inst.port

        beat = {}
        beat["ip"] = inst.ip
        beat["port"] = inst.port
        beat["scheduled"] = True
        beat["serviceName"] = inst.service_name
        beat["weight"] = inst.weight
        query["beat"] = json.dumps(beat)

        query = urllib.parse.urlencode(query)
        path = "%s?%s" % ("/nacos/v1/ns/instance/beat", query)

        conn = http.client.HTTPConnection(self._host, self._port)
        try:
            conn.request("PUT", path, headers = self._headers)
            resp = conn.getresponse()
            if self._is_ok(resp):
                logger.debug('%s heartbeat ok: %s', inst.service_name, str(resp.read(), 'utf8'))
                return True
            logger.error('%s heartbeat response: %s', inst.service_name, str(resp.read(), 'utf8'))
        except IOError as e:
            logger.error('%s heartbeat error: %s', inst.service_name, str(e))
        finally:
            conn.close()

class NacosDiscovery:

    '''
    Discovery Service implementation of nacos
    '''

    _client: NacosClient
    _scheduler: any
    _services: dict[str, list[tuple[NacosServiceRegistry, str]]]

    def __init__(self, host, port, scheduler: any, proxy_server):
        self._client = NacosClient(host, port)
        self._scheduler = scheduler
        self._proxy_server = proxy_server
        self._services = {}

    def _heartbeat_job(self, inst: NacosServiceRegistry):
        self._client.instance_beat(inst)

    def _compute_if_absent(self, service_name) -> list:
        if service_name in self._services:
            return self._services[service_name]
        lst = []
        self._services[service_name] = lst
        return lst

    def register(self, service_name, target_url, **args):
        '''Register service to discovery server
        '''
        url = urllib.parse.urlparse(target_url)
        # nacos 注册不支持路径
        host, port = urllib.parse.splitport(url.netloc)

        path = url.path.strip()
        if path == '' or path == '/': # 如果没有路径，则这个服务不需要代理
            pass
        else:
            host, port = self._proxy_server.proxy(service_name, target_url) # 代理此服务

        registry = NacosServiceRegistry(
            namespace = args.get("namespace"),
            group_name = args.get("group_name"),
            service_name = service_name,
            ip = host,
            port = port if port is not None else (443 if url.scheme == "https" else 80),
            weight = args.get("weight")
        )

        self._client.register_inst(registry)
        logger.info('Registered service %s, namespace=%s, group_name=%s', service_name, registry.namespace, registry.group_name)
        def heartbeat_job():
            logger.debug('Doing heartbeat for service %s', service_name)
            self._client.instance_beat(registry)
            self._scheduler(heartbeat_job, 5000)

        self._scheduler(heartbeat_job, 5000)
        lst = self._compute_if_absent(service_name)
        lst.append((registry, id))
